/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.alfresco.jlan.smb.dcerpc;

import org.alfresco.jlan.smb.NTTime;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.jlan.smb.TransactBuffer;
import org.alfresco.jlan.util.DataBuffer;
import org.alfresco.jlan.util.DataPacker;
import org.alfresco.jlan.util.HexDump;

/**
 * DCE Buffer Class
 *
 * <p>Used to pack and unpack DCE/RPC requests/responses.
 *
 * @author gkspencer
 */
public class DCEBuffer {

	//	Header value types

	public static final int HDR_VERMAJOR		= 0;
	public static final int HDR_VERMINOR		= 1;
	public static final int HDR_PDUTYPE			= 2;
	public static final int HDR_FLAGS			= 3;
	public static final int HDR_DATAREP			= 4;
	public static final int HDR_FRAGLEN			= 5;
	public static final int HDR_AUTHLEN			= 6;
	public static final int HDR_CALLID			= 7;
	public static final int HDR_ALLOCHINT		= 8;
	public static final int HDR_OPCODE			= 9;

	//	Header flags

	public static final int FLG_FIRSTFRAG		= 0x01;
	public static final int FLG_LASTFRAG		= 0x02;
	public static final int FLG_CANCEL			= 0x04;
	public static final int FLG_IDEMPOTENT		= 0x20;
	public static final int FLG_BROADCAST		= 0x40;

	public static final int FLG_ONLYFRAG		= 0x03;

	//	DCE/RPC header offsets

	public static final int VERSIONMAJOR		= 0;
	public static final int VERSIONMINOR		= 1;
	public static final int PDUTYPE				= 2;
	public static final int HEADERFLAGS			= 3;
	public static final int PACKEDDATAREP		= 4;
	public static final int FRAGMENTLEN			= 8;
	public static final int AUTHLEN				= 10;
	public static final int CALLID				= 12;
	public static final int DCEDATA				= 16;

	//	DCE/RPC Request offsets

	public static final int ALLOCATIONHINT		= 16;
	public static final int PRESENTIDENT		= 20;
	public static final int OPERATIONID			= 22;
	public static final int OPERATIONDATA		= 24;

	//	DCE/RPC header constants

	private static final byte VAL_VERSIONMAJOR	= 5;
	private static final byte VAL_VERSIONMINOR	= 0;
	private static final int  VAL_PACKEDDATAREP	= 0x00000010;

	//	Data alignment types

	public final static int ALIGN_NONE		= -1;
	public final static int ALIGN_SHORT		= 0;
	public final static int ALIGN_INT		= 1;
	public final static int ALIGN_LONG		= 2;

	//	Maximum string length

	public final static int MAX_STRING_LEN	= 1000;

	//	Alignment masks and rounding

	private final static int[] _alignMask  = { 0xFFFFFFFE, 0xFFFFFFFC, 0xFFFFFFF8 };
	private final static int[] _alignRound = { 1, 3, 7 };

	//	Default buffer allocation

	private static final int DEFAULT_BUFSIZE	= 8192;

	//	Maximum buffer size, used when the buffer is reset to release large buffers

	private static final int MAX_BUFFER_SIZE	= 65536;

	//	Dummy address value to use for pointers within the buffer

	private static final int DUMMY_ADDRESS		= 0x12345678;

	//	Data buffer and current read/write positions

	private byte[] m_buffer;
	private int m_base;
	private int m_pos;
	private int m_rdpos;

	//	Error status

	private int m_errorCode;

	/**
	 * Default constructor
	 */
	public DCEBuffer() {
	  m_buffer = new byte[DEFAULT_BUFSIZE];
	  m_pos    = 0;
	  m_rdpos  = 0;
	  m_base   = 0;
	}

	/**
	 * Class constructor
	 *
	 * @param siz int
	 */
	public DCEBuffer(int siz) {
	  m_buffer = new byte[siz];
	  m_pos    = 0;
	  m_rdpos  = 0;
	  m_base   = 0;
	}

	/**
	 * Class constructor
	 *
	 * @param buf byte[]
	 * @param startPos int
	 * @param len int
	 */
	public DCEBuffer(byte[] buf, int startPos, int len) {
	  m_buffer = buf;
	  m_pos    = startPos + len;
	  m_rdpos  = startPos;
	  m_base   = startPos;
	}

	/**
	 * Class constructor
	 *
	 * @param buf byte[]
	 * @param startPos int
	 */
	public DCEBuffer(byte[] buf, int startPos) {
	  m_buffer = buf;
	  m_pos    = startPos;
	  m_rdpos  = startPos;
	  m_base   = startPos;
	}

	/**
	 * Class constructor
	 *
	 * @param tbuf TransactBuffer
	 */
	public DCEBuffer(TransactBuffer tbuf) {
		DataBuffer dataBuf = tbuf.getDataBuffer();
		m_buffer = dataBuf.getBuffer();
		m_rdpos  = dataBuf.getOffset();
		m_base   = dataBuf.getOffset();
		m_pos    = m_rdpos + dataBuf.getLength();
	}

	/**
	 * Return the DCE buffer
	 *
	 * @return byte[]
	 */
	public final byte[] getBuffer() {
	  return m_buffer;
	}

	/**
	 * Return the current used buffer length
	 *
	 * @return int
	 */
	public final int getLength() {
	  return m_pos;
	}

	/**
	 * Return the read buffer position
	 *
	 * @return int
	 */
	public final int getReadPosition() {
	  return m_rdpos;
	}

	/**
	 * Return the write buffer position
	 *
	 * @return int
	 */
	public final int getWritePosition() {
	  return m_pos;
	}

	/**
	 * Return the amount of data left to read
	 *
	 * @return int
	 */
	public final int getAvailableLength() {
	  return m_pos - m_rdpos;
	}

	/**
	 * Get a byte from the buffer
	 *
	 * @param align int
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getByte(int align)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 1)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the integer value

		int bval = (m_buffer[m_rdpos++] & 0xFF);
		alignRxPosition(align);
		return bval;
	}

	/**
	 * Get a block of bytes from the buffer
	 *
	 * @param buf byte[]
	 * @param len int
	 * @return byte[]
	 * @throws DCEBufferException
	 */
	public final byte[] getBytes(byte[] buf, int len)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < len)
			throw new DCEBufferException("End of DCE buffer");

		//	Check if a return buffer should be allocated

		if ( buf == null)
			buf = new byte[len];

		//	Unpack the bytes

		for ( int i =0 ; i < len; i++)
			buf[i] = m_buffer[m_rdpos++];
		return buf;
	}

	/**
	 * Get a short from the buffer
	 *
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getShort()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 2)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the integer value

		int sval = DataPacker.getIntelShort(m_buffer,m_rdpos);
		m_rdpos += 2;
		return sval;
	}

	/**
	 * Get a short from the buffer and align the read pointer
	 *
	 * @param align int
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getShort(int align)
		throws DCEBufferException {

		//	Read the short

		int sval = getShort();

		//	Align the read position

		alignRxPosition(align);

		//	Return the short value

		return sval;
	}

	/**
	 * Get an integer from the buffer
	 *
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getInt()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 4)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the integer value

		int ival = DataPacker.getIntelInt(m_buffer,m_rdpos);
		m_rdpos += 4;
		return ival;
	}

	/**
	 * Get a pointer from the buffer
	 *
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getPointer()
		throws DCEBufferException {
		return getInt();
	}

	/**
	 * Get a pointer from the buffer and return either an empty string if the pointer is valid or null.
	 *
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getStringPointer()
		throws DCEBufferException {
	  if ( getInt() == 0)
	    return null;
		return "";
	}

	/**
	 * Get a character array header from the buffer and return either an empty string if the pointer is valid or null.
	 *
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getCharArrayPointer()
		throws DCEBufferException {

	  //	Get the array length and size

	  int len = getShort();
	  int siz = getShort();
	  return getStringPointer();
	}

	/**
	 * Get a character array from the buffer if the String variable is not null, and align on the specified boundary
	 *
	 * @param strVar String
	 * @param align int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getCharArrayNotNull(String strVar, int align)
		throws DCEBufferException {

	  //	Check if the string variable is not null

	  String str = "";

	  if ( strVar != null) {

	    //	Read the string

			str = getCharArray();

			//	Align the read position

			alignRxPosition(align);
	  }

		//	Return the string

		return str;
	}

	/**
	 * Get a long (64 bit) value from the buffer
	 *
	 * @return long
	 * @exception DCEBufferException
	 */
	public final long getLong()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 8)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the integer value

		long lval = DataPacker.getIntelLong(m_buffer,m_rdpos);
		m_rdpos += 8;
		return lval;
	}

	/**
	 * Return a DCE/RPC header value
	 *
	 * @param valTyp int
	 * @return int
	 */
	public final int getHeaderValue(int valTyp) {

		int result = -1;

		switch(valTyp) {

		  //	Version major

		  case HDR_VERMAJOR:
		  	result = (m_buffer[m_base + VERSIONMAJOR] & 0xFF);
		  	break;

		  //	Version minor

		  case HDR_VERMINOR:
		  	result = (m_buffer[m_base + VERSIONMINOR] & 0xFF);
		  	break;

			//	PDU type

			case HDR_PDUTYPE:
		  	result = (m_buffer[m_base + PDUTYPE] & 0xFF);
		  	break;

		  //	Flags

		  case HDR_FLAGS:
		  	result = (m_buffer[m_base + HEADERFLAGS] & 0xFF);
		  	break;

		  //	Data representation

		  case HDR_DATAREP:
		  	result = DataPacker.getIntelInt(m_buffer,m_base+VERSIONMINOR);
		  	break;

		  //	Authorisation length

		  case HDR_AUTHLEN:
		  	result = DataPacker.getIntelInt(m_buffer,m_base+AUTHLEN);
		  	break;

		  //	Fragment length

		  case HDR_FRAGLEN:
		  	result = DataPacker.getIntelInt(m_buffer, m_base+FRAGMENTLEN);
		  	break;

		  //	Call id

		  case HDR_CALLID:
		  	result = DataPacker.getIntelInt(m_buffer, m_base+CALLID);
		  	break;

		  //	Request allocation hint

		  case HDR_ALLOCHINT:
		  	result = DataPacker.getIntelInt(m_buffer,m_base+ALLOCATIONHINT);
		  	break;

		  //	Request opcode

		  case HDR_OPCODE:
		  	result = DataPacker.getIntelShort(m_buffer,m_base+OPERATIONID);
		  	break;
		}

		//	Return the header value

		return result;
	}

	/**
	 * Set a DCE/RPC header value
	 *
	 * @param typ int
	 * @param val int
	 */
	public final void setHeaderValue(int typ, int val) {

		switch(typ) {

		  //	Version major

		  case HDR_VERMAJOR:
		  	m_buffer[m_base + VERSIONMAJOR] = (byte) (val & 0xFF);
		  	break;

		  //	Version minor

		  case HDR_VERMINOR:
		  	m_buffer[m_base + VERSIONMINOR] = (byte) (val & 0xFF);
		  	break;

			//	PDU type

			case HDR_PDUTYPE:
		  	m_buffer[m_base + PDUTYPE] = (byte) (val & 0xFF);
		  	break;

		  //	Flags

		  case HDR_FLAGS:
		  	m_buffer[m_base + HEADERFLAGS] = (byte) (val & 0xFF);
		  	break;

		  //	Data representation

		  case HDR_DATAREP:
		  	DataPacker.putIntelInt(val,m_buffer,m_base+PACKEDDATAREP);
		  	break;

		  //	Authorisation length

		  case HDR_AUTHLEN:
		  	DataPacker.putIntelInt(val,m_buffer,m_base+AUTHLEN);
		  	break;

		  //	Fragment length

		  case HDR_FRAGLEN:
		  	DataPacker.putIntelInt(val,m_buffer,m_base+FRAGMENTLEN);
		  	break;

		  //	Call id

		  case HDR_CALLID:
		  	DataPacker.putIntelInt(val,m_buffer, m_base+CALLID);
		  	break;

		  //	Request allocation hint

		  case HDR_ALLOCHINT:
		  	DataPacker.putIntelInt(val,m_buffer,m_base+ALLOCATIONHINT);
		  	break;

		  //	Request opcode

		  case HDR_OPCODE:
		  	DataPacker.putIntelShort(val,m_buffer,m_base+OPERATIONID);
		  	break;
		}
	}

	/**
	 * Determine if this is the first fragment
	 *
	 * @return boolean
	 */
	public final boolean isFirstFragment() {
	  if (( getHeaderValue(HDR_FLAGS) & FLG_FIRSTFRAG) != 0)
	  	return true;
	  return false;
	}

	/**
	 * Determine if this is the last fragment
	 *
	 * @return boolean
	 */
	public final boolean isLastFragment() {
	  if (( getHeaderValue(HDR_FLAGS) & FLG_LASTFRAG) != 0)
	  	return true;
	  return false;
	}

	/**
	 * Determine if this is the only fragment in the request
	 *
	 * @return boolean
	 */
	public final boolean isOnlyFragment() {
	  if (( getHeaderValue(HDR_FLAGS) & FLG_ONLYFRAG) == FLG_ONLYFRAG)
	  	return true;
	  return false;
	}

	/**
	 * Check if the status indicates that there are more entries available
	 *
	 * @return boolean
	 */
	public final boolean hasMoreEntries() {
	  return getStatusCode() == SMBStatus.Win32MoreEntries ? true : false;
	}

	/**
	 * Check if the status indicates success
	 *
	 * @return boolean
	 */
	public final boolean hasSuccessStatus() {
	  return getStatusCode() == SMBStatus.NTSuccess ? true : false;
	}

	/**
	 * Skip over a number of bytes
	 *
	 * @param cnt int
	 * @exception DCEBufferException
	 */
	public final void skipBytes(int cnt)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < cnt)
			throw new DCEBufferException("End of DCE buffer");

		//	Skip bytes

		m_rdpos += cnt;
	}

	/**
	 * Skip over a pointer
	 *
	 * @exception DCEBufferException
	 */
	public final void skipPointer()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 4)
			throw new DCEBufferException("End of DCE buffer");

		//	Skip the 32bit pointer value

		m_rdpos += 4;
	}

	/**
	 * Set the read position
	 *
	 * @param pos int
	 * @exception DCEBufferException
	 */
	public final void positionAt(int pos)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length < pos)
			throw new DCEBufferException("End of DCE buffer");

		//	Set the read position

		m_rdpos = pos;
	}

	/**
	 * Get a number of Unicode characters from the buffer and return as a string
	 *
	 * @param len int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getChars(int len)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < (len * 2))
			throw new DCEBufferException("End of DCE buffer");

		//	Build up the return string

		StringBuffer str = new StringBuffer(len);
		char curChar;

		while ( len-- > 0) {

      //  Get a Unicode character from the buffer

      curChar = (char) ((m_buffer[m_rdpos + 1] << 8) + m_buffer[m_rdpos]);
      m_rdpos += 2;

      //  Add the character to the string

			str.append(curChar);
		}

		//	Return the string

		return str.toString();
	}

	/**
	 * Get the status code from the end of the data block
	 *
	 * @return int
	 */
	public final int getStatusCode() {

		//	Read the integer value at the end of the buffer

		int ival = DataPacker.getIntelInt(m_buffer,m_pos - 4);
		return ival;
	}

	/**
	 * Get a string from the buffer
	 *
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getString()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 12)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the string

		int maxLen = getInt();
		skipBytes(4);						//	offset
		int strLen = getInt();

		String str = DataPacker.getUnicodeString(m_buffer,m_rdpos, strLen);
		m_rdpos += (strLen * 2);
		return str;
	}

	/**
	 * Get a character array from the buffer
	 *
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getCharArray()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 12)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the string

		int maxLen = getInt();
		skipBytes(4);						//	offset
		int strLen = getInt();	//	in unicode chars

		String str = null;
		if ( strLen > 0) {
		  str = DataPacker.getUnicodeString(m_buffer,m_rdpos, strLen);
		  m_rdpos += (strLen * 2);
		}
		return str;
	}

	/**
	 * Get a character array from the buffer and align on the specified boundary
	 *
	 * @param align int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getCharArray(int align)
		throws DCEBufferException {

		//	Read the string

		String str = getCharArray();

		//	Align the read position

		alignRxPosition(align);

		//	Return the string

		return str;
	}

	/**
	 * Get a string from the buffer and align on the specified boundary
	 *
	 * @param align int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getString(int align)
		throws DCEBufferException {

		//	Read the string

		String str = getString();

		//	Align the read position

		alignRxPosition(align);

		//	Return the string

		return str;
	}

	/**
	 * Get a string from the buffer if the String variable is not null, and align on the specified boundary
	 *
	 * @param strVar String
	 * @param align int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getStringNotNull(String strVar, int align)
		throws DCEBufferException {

	  //	Check if the string variable is not null

	  String str = "";

	  if ( strVar != null) {

	    //	Read the string

			str = getString();

			//	Align the read position

			alignRxPosition(align);
	  }

		//	Return the string

		return str;
	}

	/**
	 * Get a string from a particular position in the buffer
	 *
	 * @param pos int
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getStringAt(int pos)
		throws DCEBufferException {

		//	Check if position is within the buffer

		if ( m_buffer.length < pos)
			throw new DCEBufferException("Buffer offset out of range, " + pos);

		//	Unpack the string

		String str = DataPacker.getUnicodeString(m_buffer,pos,MAX_STRING_LEN);
		return str;
	}

	/**
	 * Read a Unicode string header and return the string length. -1 indicates a null pointer in the
	 * string header.
	 *
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int getUnicodeHeaderLength()
		throws DCEBufferException {

		//	Check if there is enough data in the buffer for the Unicode header

		if ( m_buffer.length - m_rdpos < 8)
			throw new DCEBufferException("End of DCE buffer");

		//	Get the string length

		int len = (int) DataPacker.getIntelShort(m_buffer,m_rdpos);
		m_rdpos += 4;		// skip the max length too
		int ptr = DataPacker.getIntelInt(m_buffer, m_rdpos);
		m_rdpos += 4;

		//	Check if the pointer is valid

		if ( ptr == 0)
			return -1;
		return len;
	}

	/**
	 * Get a unicode string from the current position in the buffer
	 *
	 * @return String
	 * @exception DCEBufferException
	 */
	public final String getUnicodeString()
		throws DCEBufferException {

		//	Check if there is any buffer to read

		if ( m_buffer.length - m_rdpos <= 0)
			throw new DCEBufferException("No more buffer");

		//	Unpack the string

		String str = DataPacker.getUnicodeString(m_buffer,m_rdpos,MAX_STRING_LEN);
		if ( str != null)
			m_rdpos += (str.length() * 2) + 2;
		return str;
	}

	/**
	 * Get a data block from the buffer and align on the specified boundary
	 *
	 * @param align int
	 * @return byte[]
	 * @exception DCEBufferException
	 */
	public final byte[] getDataBlock(int align)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 12)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the data block

		int len = getInt();
		m_rdpos += 8;		//	skip undoc and max_len ints

		//	Copy the raw data block

		byte[] dataBlk = null;

		if ( len > 0) {

			//	Allocate the data block buffer

			dataBlk = new byte[len];

			//	Copy the raw data

			System.arraycopy(m_buffer,m_rdpos,dataBlk,0,len);
		}

		//	Update the buffer position and align

		m_rdpos += len;
		alignRxPosition(align);
		return dataBlk;
	}

	/**
	 * Get a UUID from the buffer
	 *
	 * @param readVer boolean
	 * @return UUID
	 * @exception DCEBufferException
	 */
	public final UUID getUUID(boolean readVer)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		int len = UUID.UUID_LENGTH_BINARY;
		if ( readVer == true)
			len += 4;

		if ( m_buffer.length - m_rdpos < len)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the UUID

		UUID uuid = new UUID(m_buffer,m_rdpos);
		m_rdpos += UUID.UUID_LENGTH_BINARY;

		if ( readVer == true) {
		  int ver = getInt();
		  uuid.setVersion(ver);
		}

		return uuid;
	}

	/**
	 * Get an NT 64bit time value. If the value is valid then convert to a Java time value
	 *
	 * @return long
	 * @throws DCEBufferException
	 */
	public final long getNTTime()
		throws DCEBufferException {

	  //	Get the raw NT time value

	  long ntTime = getLong();
	  if ( ntTime == 0 || ntTime == NTTime.InfiniteTime)
	    return ntTime;

	  //	Convert the time to a Java time value

	  return NTTime.toJavaDate(ntTime);
	}

	/**
	 * Get a byte structure that has a header
	 *
	 * @param buf byte[]
	 * @throws DCEBufferException
	 */
	public final byte[] getByteStructure(byte[] buf)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < 12)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the header

		int maxLen = getInt();
		skipBytes(4);						//	offset
		int bytLen = getInt();

		byte[] bytBuf = buf;
		if ( bytBuf.length < bytLen)
		  bytBuf = new byte[bytLen];
		return getBytes(bytBuf, bytLen);
	}

	/**
	 * Get a handle from the buffer
	 *
	 * @param handle PolicyHandle
	 * @exception DCEBufferException
	 */
	public final void getHandle(PolicyHandle handle)
		throws DCEBufferException {

		//	Check if there is enough data in the buffer

		if ( m_buffer.length - m_rdpos < PolicyHandle.POLICY_HANDLE_SIZE)
			throw new DCEBufferException("End of DCE buffer");

		//	Unpack the policy handle

		m_rdpos = handle.loadPolicyHandle(m_buffer,m_rdpos);
	}

	/**
	 * Copy data from the DCE buffer to the user buffer, and update the current read position.
	 *
	 * @param buf byte[]
	 * @param off int
	 * @param cnt int
	 * @return int
	 * @exception DCEBufferException
	 */
	public final int copyData(byte[] buf, int off, int cnt)
		throws DCEBufferException {

		//	Check if there is any more data to copy

		if ( m_rdpos == m_pos)
			return 0;

		//	Calculate the amount of data to copy

		int siz = m_pos - m_rdpos;
		if ( siz > cnt)
			siz = cnt;

		//	Copy the data to the user buffer and update the current read position

		System.arraycopy(m_buffer,m_rdpos,buf,off,siz);
		m_rdpos += siz;

		//	Return the amount of data copied

		return siz;
	}

	/**
	 * Append a raw data block to the buffer
	 *
	 * @param buf byte[]
	 * @param off int
	 * @param len int
	 * @exception DCEBufferException
	 */
	public final void appendData(byte[] buf, int off, int len)
		throws DCEBufferException {

		//	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < len)
	  	extendBuffer(len);

		//	Copy the data to the buffer and update the current write position

		System.arraycopy(buf,off,m_buffer,m_pos,len);
		m_pos += len;
	}

	/**
	 * Append an integer to the buffer
	 *
	 * @param ival int
	 */
	public final void putInt(int ival) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 4)
	  	extendBuffer();

	  //	Pack the integer value

	  DataPacker.putIntelInt(ival,m_buffer,m_pos);
	  m_pos += 4;
	}

	/**
	 * Append a byte value to the buffer
	 *
	 * @param bval int
	 */
	public final void putByte(int bval) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 1)
	  	extendBuffer();

	  //	Pack the short value

		m_buffer[m_pos++] = (byte) (bval & 0xFF);
	}

	/**
	 * Append a byte value to the buffer and align to the specified boundary
	 *
	 * @param bval byte
	 * @param align int
	 */
	public final void putByte(byte bval, int align) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 1)
	  	extendBuffer();

	  //	Pack the short value

		m_buffer[m_pos++] = bval;
		alignPosition(align);
	}

	/**
	 * Append a byte value to the buffer and align to the specified boundary
	 *
	 * @param bval int
	 * @param align int
	 */
	public final void putByte(int bval, int align) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 1)
	  	extendBuffer();

	  //	Pack the short value

		m_buffer[m_pos++] = (byte) (bval & 0xFF);
		alignPosition(align);
	}

	/**
	 * Append a block of bytes to the buffer
	 *
	 * @param bval byte[]
	 * @param len int
	 */
	public final void putBytes(byte[] bval, int len) {

		//	Check if there is enough space in the buffer

		if ( m_buffer.length - m_pos < len)
			extendBuffer();

		//	Pack the bytes

		for ( int i = 0; i < len; i++)
			m_buffer[m_pos++] = bval[i];
	}

	/**
	 * Append a block of bytes to the buffer
	 *
	 * @param bval byte[]
	 * @param len int
	 * @param align int
	 */
	public final void putBytes(byte[] bval, int len, int align) {

		//	Check if there is enough space in the buffer

		if ( m_buffer.length - m_pos < len)
			extendBuffer();

		//	Pack the bytes

		for ( int i = 0; i < len; i++)
			m_buffer[m_pos++] = bval[i];

		//	Align the new buffer position

		alignPosition(align);
	}

	/**
	 * Append a short value to the buffer
	 *
	 * @param sval int
	 */
	public final void putShort(int sval) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 2)
	  	extendBuffer();

	  //	Pack the short value

	  DataPacker.putIntelShort(sval,m_buffer,m_pos);
	  m_pos += 2;
	}

	/**
	 * Append a DCE string to the buffer
	 *
	 * @param str String
	 */
	public final void putString(String str) {

	  //	Check if there is enough space in the buffer

		int reqLen = (str.length() * 2) + 24;

	  if ( m_buffer.length - m_pos < reqLen)
	  	extendBuffer(reqLen);

	  //	Pack the string

	  m_pos = DCEDataPacker.putDCEString(m_buffer,m_pos,str,false);
	}

	/**
	 * Append a DCE string to the buffer and align to the specified boundary
	 *
	 * @param str String
	 * @param align int
	 */
	public final void putString(String str, int align) {

	  //	Check if there is enough space in the buffer

		int reqLen = (str.length() * 2) + 24;

	  if ( m_buffer.length - m_pos < reqLen)
	  	extendBuffer(reqLen);

	  //	Pack the string

	  m_pos = DCEDataPacker.putDCEString(m_buffer,m_pos,str,false);

	  //	Align the new buffer position

	  alignPosition(align);
	}

	/**
	 * Append a DCE string to the buffer, specify whether the nul is included in the string length or not
	 *
	 * @param str String
	 * @param align int
	 * @param incNul boolean
	 */
	public final void putString(String str, int align, boolean incNul) {

	  //	Check if there is enough space in the buffer

	  int reqLen = (str.length() * 2) + 24;
	  if ( incNul)
	  	reqLen += 2;

	  if ( m_buffer.length - m_pos < reqLen)
	  	extendBuffer( reqLen);

	  //	Pack the string

	  m_pos = DCEDataPacker.putDCEString(m_buffer,m_pos,str,incNul);

	  //	Align the new buffer position

	  alignPosition(align);
	}

	/**
	 * Append string return buffer details. Some DCE/RPC requests incorrectly send output parameters as input.
	 *
	 * @param len int
	 * @param align int
	 */
	public final void putStringReturn(int len, int align) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 20)
	  	extendBuffer();

	  //	Pack the string return details

		DataPacker.putIntelInt(len, m_buffer, m_pos);
		DataPacker.putZeros(m_buffer,m_pos+4, 8);
		DataPacker.putIntelInt(DUMMY_ADDRESS, m_buffer, m_pos + 12);
		m_pos += 16;

	  //	Align the new buffer position

	  alignPosition(align);
	}

	/**
	 * Append a DCE string header to the buffer
	 *
	 * @param str String
	 * @param incNul boolean
	 */
	public final void putUnicodeHeader(String str, boolean incNul) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 8)
	  	extendBuffer();

		//	Calculate the string length in bytes

		int sLen = 0;
		if ( str != null)
			sLen = str.length() * 2;

	  //	Pack the string header

		if ( str != null)
	  	DataPacker.putIntelShort(incNul ? sLen + 2 : sLen,m_buffer,m_pos);
	  else
	  	DataPacker.putIntelShort(0,m_buffer,m_pos);

	  DataPacker.putIntelShort(sLen != 0 ? sLen + 2 : 0,m_buffer,m_pos + 2);
	  DataPacker.putIntelInt(str != null ? DUMMY_ADDRESS : 0,m_buffer,m_pos + 4);

	  m_pos += 8;
	}

	/**
	 * Append a Unicode return string header to the buffer.  Some DCE/RPC requests incorrectly send output
	 * parameters as input.
	 *
	 * @param len int
	 */
	public final void putUnicodeReturn(int len) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 8)
	  	extendBuffer();

	  //	Pack the string header

  	DataPacker.putIntelShort(0,m_buffer,m_pos);
	  DataPacker.putIntelShort(len,m_buffer,m_pos + 2);
	  DataPacker.putIntelInt(DUMMY_ADDRESS,m_buffer,m_pos + 4);

	  m_pos += 8;
	}

	/**
	 * Append a DCE string header to the buffer
	 *
	 * @param len int
	 */
	public final void putUnicodeHeader(int len) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 8)
	  	extendBuffer();

		//	Calculate the string length in bytes

		int sLen = len * 2;

	  //	Pack the string header

  	DataPacker.putIntelShort(sLen,m_buffer,m_pos);
	  DataPacker.putIntelShort(sLen + 2,m_buffer,m_pos + 2);
	  DataPacker.putIntelInt(sLen != 0 ? DUMMY_ADDRESS : 0,m_buffer,m_pos + 4);

	  m_pos += 8;
	}

	/**
	 * Append Unicode characters to the buffer
	 *
	 * @param str String
	 * @param align int
	 */
	public final void putUnicodeBytes(String str, int align) {

		//  Check if there is enough space in the buffer

		if ( m_buffer.length - m_pos < str.length() * 2)
			extendBuffer();

		// Pack the Unicode characters

		m_pos = DataPacker.putString(str, m_buffer, m_pos, false, true);

		//  Align the buffer position

		alignPosition(align);
	}

	/**
	 * Append an ASCII string to the DCE buffer
	 *
	 * @param str String
	 * @param incNul boolean
	 */
	public final void putASCIIString(String str, boolean incNul) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < (str.length() + 1))
	  	extendBuffer(str.length() + 2);

	  //	Pack the string

	  m_pos = DataPacker.putString(str,m_buffer,m_pos,incNul);
	}

	/**
	 * Append an ASCII string to the DCE buffer, and align on the specified boundary
	 *
	 * @param str String
	 * @param incNul boolean
	 * @param align int
	 */
	public final void putASCIIString(String str, boolean incNul, int align) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < (str.length() + 1))
	  	extendBuffer(str.length() + 8);

	  //	Pack the string

	  m_pos = DataPacker.putString(str,m_buffer,m_pos,incNul);

	  //	Align the buffer position

	  alignPosition(align);
	}

	/**
	 * Append a pointer to the buffer.
	 *
	 * @param obj Object
	 */
	public final void putPointer(Object obj) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 4)
	  	extendBuffer();

	  //	Check if the object is valid, if not then put a null pointer into the buffer

	  if ( obj == null)
	  	DataPacker.putZeros(m_buffer, m_pos, 4);
	  else
	  	DataPacker.putIntelInt(DUMMY_ADDRESS,m_buffer,m_pos);
	  m_pos += 4;
	}

	/**
	 * Append a pointer to the buffer.
	 *
	 * @param notNull boolean
	 */
	public final void putPointer(boolean notNull) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 4)
	  	extendBuffer();

	  //	Check if the object is valid, if not then put a null pointer into the buffer

	  if ( notNull == false)
	  	DataPacker.putZeros(m_buffer, m_pos, 4);
	  else
	  	DataPacker.putIntelInt(DUMMY_ADDRESS,m_buffer,m_pos);
	  m_pos += 4;
	}

	/**
	 * Append a UUID to the buffer
	 *
	 * @param uuid UUID
	 * @param writeVer boolean
	 */
	public final void putUUID(UUID uuid, boolean writeVer) {

	  //	Check if there is enough space in the buffer

		int len = UUID.UUID_LENGTH_BINARY;
		if ( writeVer == true)
			len += 4;

	  if ( m_buffer.length - m_pos < len)
	  	extendBuffer();

	  //	Pack the UUID

	  m_pos = uuid.storeUUID(m_buffer,m_pos,writeVer);
	}

	/**
	 * Append a policy handle to the buffer
	 *
	 * @param handle PolicyHandle
	 */
	public final void putHandle(PolicyHandle handle) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < PolicyHandle.POLICY_HANDLE_SIZE)
	  	extendBuffer(PolicyHandle.POLICY_HANDLE_SIZE);

	  //	Pack the policy handle

		m_pos = handle.storePolicyHandle(m_buffer,m_pos);
	}

	/**
	 * Append a DCE buffer to the current DCE buffer
	 *
	 * @param buf DCEBuffer
	 */
	public final void putBuffer(DCEBuffer buf) {
	  try {
  	  appendData(buf.getBuffer(),buf.getReadPosition(), buf.getLength());
	  }
	  catch (DCEBufferException ex) {
	  }
	}

	/**
	 * Append an error status to the buffer, also sets the error status value
	 *
	 * @param sts int
	 */
	public final void putErrorStatus(int sts) {

	  //	Check if there is enough space in the buffer

	  if ( m_buffer.length - m_pos < 4)
	  	extendBuffer();

	  //	Pack the status value

	  DataPacker.putIntelInt(sts,m_buffer,m_pos);
	  m_pos += 4;

	  //	Save the status value

	  m_errorCode = sts;
	}


	/**
	 * Append a DCE header to the buffer
	 *
	 * @param pdutyp int
	 * @param callid int
	 */
	public final void putHeader(int pdutyp, int callid) {
	  m_buffer[m_pos++] = VAL_VERSIONMAJOR;
	  m_buffer[m_pos++] = VAL_VERSIONMINOR;
	  m_buffer[m_pos++] = (byte) (pdutyp & 0xFF);
	  m_buffer[m_pos++] = 0;

    DataPacker.putIntelInt(VAL_PACKEDDATAREP,m_buffer, m_pos);
    m_pos += 4;

		DataPacker.putZeros(m_buffer,m_pos,4);
    m_pos += 4;

    DataPacker.putIntelInt(callid, m_buffer, m_pos);
    m_pos += 4;
	}

	/**
	 * Append a bind header to the buffer
	 *
	 * @param callid int
	 */
	public final void putBindHeader(int callid) {
	  putHeader(DCECommand.BIND, callid);
	}

	/**
	 * Append a bind acknowlegde header to the buffer
	 *
	 * @param callid int
	 */
	public final void putBindAckHeader(int callid) {
	  putHeader(DCECommand.BINDACK, callid);
	}

	/**
	 * Append a request header to the buffer
	 *
	 * @param callid int
	 * @param opcode int
	 * @param allocHint int
	 */
	public final void putRequestHeader(int callid, int opcode, int allocHint) {
	  putHeader(DCECommand.REQUEST, callid);
	  DataPacker.putIntelInt(allocHint,m_buffer,m_pos);
	  m_pos += 4;
	  DataPacker.putZeros(m_buffer,m_pos,2);
	  m_pos += 2;
	  DataPacker.putIntelShort(opcode,m_buffer,m_pos);
	  m_pos += 2;
	}

	/**
	 * Append a response header to the buffer
	 *
	 * @param callid int
	 * @param allocHint int
	 */
	public final void putResponseHeader(int callid, int allocHint) {
	  putHeader(DCECommand.RESPONSE, callid);
	  DataPacker.putIntelInt(allocHint,m_buffer,m_pos);
	  m_pos += 4;
	  DataPacker.putZeros(m_buffer,m_pos,4);
	  m_pos += 4;
	}

	/**
	 * Append zero integers to the buffer
	 *
	 * @param cnt int
	 */
	public final void putZeroInts(int cnt) {

	  //	Check if there is enough space in the buffer

		int bytCnt = cnt * 4;
	  if ( m_buffer.length - m_pos < bytCnt)
	  	extendBuffer(bytCnt * 2);

	  //	Pack the zero integer values

	  DataPacker.putZeros(m_buffer,m_pos, bytCnt);
	  m_pos += bytCnt;
	}

	/**
	 * Reset the buffer pointers to reuse the buffer
	 */
	public final void resetBuffer() {

	  //	Reset the read/write positions

	  m_pos   = 0;
	  m_rdpos = 0;

	  //	If the buffer is over sized release it and allocate a standard sized buffer

	  if ( m_buffer.length >= MAX_BUFFER_SIZE)
	    m_buffer = new byte[DEFAULT_BUFSIZE];
	}

	/**
	 * Set the new write position
	 *
	 * @param pos int
	 */
	public final void setWritePosition(int pos) {
		m_pos = pos;
	}

	/**
	 * Update the write position by the specified amount
	 *
	 * @param len int
	 */
	public final void updateWritePosition(int len) {
		m_pos += len;
	}

	/**
	 * Determine if there is an error status set
	 *
	 * @return boolean
	 */
	public final boolean hasErrorStatus() {
	  return m_errorCode != 0 ? true : false;
	}

	/**
	 * Return the error status code
	 *
	 * @return int
	 */
	public final int getErrorStatus() {
	  return m_errorCode;
	}

	/**
	 * Set the error status code
	 *
	 * @param sts int
	 */
	public final void setErrorStatus(int sts) {
	  m_errorCode = sts;
	}

	/**
	 * Extend the DCE buffer by the specified amount
	 *
	 * @param ext int
	 */
	private final void extendBuffer(int ext) {

	  //	Create a new buffer of the required size

	  byte[] newBuf = new byte[m_buffer.length + ext];

	  //	Copy the data from the current buffer to the new buffer

	  System.arraycopy(m_buffer, 0, newBuf, 0, m_buffer.length);

	  //	Set the new buffer to be the main buffer

	  m_buffer = newBuf;
	}

	/**
	 * Extend the DCE buffer, double the currently allocated buffer size
	 */
	private final void extendBuffer() {
	  extendBuffer(m_buffer.length * 2);
	}

	/**
	 * Align the current buffer position on the specified boundary
	 *
	 * @param align int
	 */
	private final void alignPosition(int align) {

	  //	Range check the alignment

	  if ( align < 0 || align > 2)
	  	return;

	  //	Align the buffer position on the required boundary

	  m_pos = (m_pos + _alignRound[align]) & _alignMask[align];
	}

	/**
	 * Align the receive buffer position on the specified boundary
	 *
	 * @param align int
	 */
	private final void alignRxPosition(int align) {

	  //	Range check the alignment

	  if ( align < 0 || align > 2 || m_rdpos >= m_buffer.length)
	  	return;

	  //	Align the buffer position on the required boundary

	  m_rdpos = (m_rdpos + _alignRound[align]) & _alignMask[align];
	}

	/**
	 * Dump the DCE buffered data
	 */
	public final void Dump() {
	  int len = getLength();
	  if ( len == 0)
	  	len = 24;
	  HexDump.Dump(getBuffer(),len,m_base);
	}
}
